from __future__ import with_statement
import os
import re
import glob
import shutil
import logging.config
import ConfigParser
from pprint import pformat

import pkg_resources
import clevercss
from paste.script import command
from paste.util.template import paste_script_template_renderer

def configure_logging():
    if not logging._handlers:
        print 'Configure logging'
        global log, db
        logging.config.fileConfig('development.ini')
        log = logging.getLogger('clutch.command')
        from clutch import database as db
        print 'Logging configured'
    else:
        print 'Logging already configured'
configure_logging()


from clutch import database as db
from clutch.util import bag, walk_pkg
from clutch.globals import C, G
from clutch.wsgi import WSGIApplication
from clutch.view import engines

try:
    from IPython.Shell import IPShellEmbed
    def shell(name, ns):
        console = IPShellEmbed(argv=[], banner='Clutch Shell for %s' % name, user_ns=ns)
        console()
except ImportError:
    import code
    def shell(name, ns):
        print 'Basic shell for %s' % name
        console = code.InteractiveConsole(ns)
        console.interact()

def available_instances():
    return sorted(fn[5:-4] for fn in glob.glob('apps/*.ini'))

def load_mtab():
    def _():
        cp = ConfigParser.ConfigParser()
        cp.read('routes.ini')
        for mount_point, config in cp.items('composite:main'):
            if mount_point == 'use': continue
            re_text = r'config:apps/(.+)\.ini'
            mo = re.match(re_text, config)
            assert mo is not None, 'Expected %s, got %s' % (re_text, config)
            instance = mo.group(1)
            yield mount_point, instance
    return dict(_())

def save_mtab(mtab):
    with open('routes.ini', 'wb') as fp:
        print >> fp, '[composite:main]'
        print >> fp, 'use = egg:Paste#urlmap'
        for mount_point, inst in mtab.iteritems():
            print >> fp, '%s = config:apps/%s.ini' % (mount_point, inst)

class MountCommand(command.Command):
    summary='Mount INIFILE at MOUNTPOINT'
    min_args=0
    max_args=2
    group_name = 'Clutch'
    usage = '[ INIFILE MOUNTPOINT ]'

    parser = command.Command.standard_parser(verbose=True)

    def command(self):
        mtab = load_mtab()
        if self.args:
            inst, mp = self.args
            assert inst in available_instances(), 'Unknown instance %s' % inst
            mtab[mp] = inst
        for mount_point, config in sorted(mtab.items(), key=lambda (k,v):(len(k),k)):
            print '%s => %s' % (mount_point, config)
        save_mtab(mtab)

class UnmountCommand(command.Command):
    summary='Unmount INSTANCE or Unmount MOUNTPOINT'
    min_args=1
    max_args=1
    group_name = 'Clutch'
    usage = 'INSTANCE | MOUNTPOINT'

    parser = command.Command.standard_parser(verbose=True)

    def command(self):
        arg = self.args[0]
        mtab = load_mtab()
        mtab = dict((k,v) for k,v in mtab.iteritems()
                    if arg not in (k,v))
        save_mtab(mtab)

class ListCommand(command.Command):
    summary='List available instances (install more with paster setup)'
    min_args = max_args = 0
    group_name = 'Clutch'

    parser = command.Command.standard_parser(verbose=True)

    def command(self):
        print 'Available instances:'
        for inst in available_instances():
            print '    ' + inst

class RefreshCommand(command.Command):
    summary = 'Build a public/ directory containing all static files for the project'
    min_args=0
    max_args=0
    group_name = 'Clutch'

    parser = command.Command.standard_parser(verbose=True)

    def command(self):
        # Remove the public/ directory
        if os.path.exists('public'):
            shutil.rmtree('public')
        # Copy in the Clutch static files
        self.refresh_package('clutch', 'public')
        mtab = load_mtab()
        apps = ((mp, WSGIApplication.create('apps/' + name + '.ini', 'main',
                                            in_wsgi_server=False))
                for mp, name in mtab.iteritems())
        for mount_point, app in apps:
            app_public = os.path.join('public', mount_point[1:])
            with app.context():
                # G.application.app.templates = G.application.app.static_templates
                self.refresh_package(app.app_config.package_name, app_public)

    def refresh_package(self, pkg, dst):
        # Copy all the public files
        public_base = pkg_resources.resource_filename(pkg, 'public')
        for dirpath, dirnames, filenames in os.walk(public_base):
            if '.svn' in dirnames:
                dirnames.remove('.svn')
            relative_path = dirpath[len(public_base)+1:]
            dst_path = os.path.join(dst, relative_path)
            if not os.path.exists(dst_path):
                log.info('Create %s', dst_path)
                os.makedirs(dst_path)
            for fn in filenames:
                src_fn = os.path.join(dirpath, fn)
                dst_fn = os.path.join(dst, relative_path, fn)
                log.info('... copy %s => %s', os.path.join(relative_path, fn), dst_fn)
                shutil.copy2(src_fn, dst_fn)
        # Compile all the static templates
        for dirpath, dirnames, filenames in walk_pkg(pkg, 'static_templates'):
            if '.svn' in dirnames:
                dirnames.remove('.svn')
            dirpath = dirpath[len('static_templates/'):]
            dstpath = os.path.join(dst, dirpath)
            if not os.path.exists(dstpath):
                log.info('Create %s', dstpath)
                os.makedirs(dstpath)
            for fn in filenames:
                self.compile_static_template(dirpath, fn, dstpath)

    def compile_static_template(self, dirpath, fn, dstpath):
        if ':' in fn:
            name, template = fn.split(':', 1)
        else:
            name, template = (), fn
        engine = engines.get(name, template)
        loader = engine.loader(
            G.application.app_config.package_name,
            G.application.app_config.static_templates.objname)
        srcpath = os.path.join(dirpath, template)
        dstpath = os.path.join(dstpath, template)
        template = loader.template(srcpath)
        parts = template.render({})
        # Special handling for CleverCSS
        if fn.endswith('.clever'):
            clever_text = ''.join(parts)
            css_text = clevercss.convert(clever_text)
            dstpath = dstpath[:-7] + '.css'
            parts = [ css_text ]
        print '... compile %s => %s' % (srcpath, dstpath)
        with open(dstpath, 'wb') as wfp:
            for part in parts:
                wfp.write(str(part))

class SetupCommand(command.Command):
    summary = 'Setup a plugin app within a project'
    min_args=1
    max_args=2
    group_name = 'Clutch'
    usage = 'REQUIREMENT [ INSTANCE_NAME ]'

    parser = command.Command.standard_parser(verbose=True)
    parser.add_option('--app',
                      dest='app',
                      default='main',
                      help='The Clutch application to install')

    def command(self):
        app = self.options.app
        # Load the distribution
        if len(self.args) == 2:
            req, inst = self.args
            dist = pkg_resources.get_distribution(req)
        else:
            req = self.args[0]
            dist = pkg_resources.get_distribution(req)
            if app != 'main':
                inst = app
            else:
                inst = dist.key
        # Generate the INI files
        src_ini_tmpl = dist.get_metadata('clutch_deploy_%s_config.ini_tmpl' % app)
        src_ini_txt = paste_script_template_renderer(src_ini_tmpl, dict())
        print >> open('apps/%s.ini' % inst, 'w'), src_ini_txt
        self.update_requirements(req, dist)

    def update_requirements(self, req, dist):
        if req.isalpha():
            req = dist.project_name + '>=' + dist.version
        ns = { '__builtins__': None }
        exec open('requirements.py') in ns, ns
        current_requirements = set(ns['install_requires'])
        current_requirements.add(req)
        with open('requirements.py', 'w') as fp:
            print >> fp, 'install_requires = ' + pformat(list(current_requirements))

class ClutchCommand(command.Command):
    min_args=0
    group_name = 'Clutch'
    usage = '[APP1 [APP2...]]'
    
    parser = command.Command.standard_parser(verbose=True)

    def command(self):
        if self.args:
            appnames = self.args
        else:
            appnames = available_instances()
        configs = [ 'apps/%s.ini' % a for a in appnames ]
        apps = [ WSGIApplication.create(config, 'main',
                                        in_wsgi_server=False)
                 for config, appname in zip(configs, appnames) ]
        for appname, app in zip(appnames, apps):
            with app.context():
                self._invoke(appname, app)

    def _invoke(self, appname, app):
        raise NotImplementedError, '_invoke'

class ReloadCommand(ClutchCommand):

    summary = 'Reload the database'

    def _invoke(self, name, app):
        log.info('Reloading %s', name)
        if app.model is None: return
        app.model.session.begin()
        try:
            app.commands.reload()
            app.model.session.commit()
        except:
            app.model.session.rollback()
            raise

class ShellCommand(ClutchCommand):

    max_args=1
    summary = 'Invoke an interactive shell'

    def _invoke(self, name, app):
        ns = dict(C=C,G=G)
        if app.model:
            ns.update(app.model.__dict__)
        shell(name, ns)

def default_reload():
    G.application.model.metadata.drop_all()
    G.application.model.metadata.create_all()
    G.application.model.session.commit()
    G.application.model.session.begin()

